package oneapm

import (
	"errors"
	"google.golang.org/protobuf/proto"
	"strconv"
	"strings"

	"go.opentelemetry.io/collector/model/pdata"

	oneProto "go.opentelemetry.io/collector/pkg/translator/oneapm/proto"

	"fmt"
	jsonIter "github.com/json-iterator/go"
	"time"
)

// OneToMetrics JavaAgentData接口数据转ot metric格式；contentType: "application/x-protobuf" | "application/json"
func OneToMetrics(body []byte, contentType string) (pdata.Metrics, error) {
	var javaAgentData oneProto.JavaAgentDataRequest
	var err error
	if strings.Contains(contentType, jsonContentType) {
		json := jsonIter.ConfigCompatibleWithStandardLibrary
		err = json.Unmarshal(body, &javaAgentData)
	} else if strings.Contains(contentType, pbContentType) {
		err = proto.Unmarshal(body, &javaAgentData)
	} else {
		err = errors.New("unSupport data format")
	}
	md := pdata.NewMetrics()
	if err != nil {
		loggerOneApm.Debug("body", body)
		return md, err
	}
	resourceMetricsSlice := md.ResourceMetrics()
	formatOTMetric(&javaAgentData, &resourceMetricsSlice)
	return md, nil
}

func formatOTMetric(javaAgentData *oneProto.JavaAgentDataRequest, resourceMetricsSlice *pdata.ResourceMetricsSlice) {
	for key, identifier := range javaAgentData.GetIdentifier() {
		resourceMetrics := resourceMetricsSlice.AppendEmpty()
		resource := resourceMetrics.Resource()
		attributes := resource.Attributes()
		if len(javaAgentData.GetResource()) > key {
			insertIdentifierAttribute(javaAgentData.GetResource()[key], identifier, &attributes)
		}
		instrumentationLibrarySpansSlice := resourceMetrics.InstrumentationLibraryMetrics()
		instrumentationLibrarySpans := instrumentationLibrarySpansSlice.AppendEmpty()
		metricSlice := instrumentationLibrarySpans.Metrics()
		for _, v := range javaAgentData.Gcs {
			metric := metricSlice.AppendEmpty()
			metric.SetName("process.runtime.jvm.gc.time")
			metric.SetDataType(pdata.MetricDataTypeGauge)
			metric.SetUnit("second")
			metric.SetDescription("Time spent in a given JVM garbage collector in milliseconds.")
			numberDataPoint := metric.Gauge().DataPoints().AppendEmpty()
			numberDataPoint.SetTimestamp(pdata.Timestamp(time.Now().UnixNano()))
			value, _ := strconv.ParseFloat(fmt.Sprintf("%.2f", v.GetCollectionTime()), 64)
			numberDataPoint.SetDoubleVal(value)

			metric = metricSlice.AppendEmpty()
			metric.SetDataType(pdata.MetricDataTypeGauge)
			metric.SetName("process.runtime.jvm.gc.count")
			metric.SetDescription("The number of collections that have occurred for a given JVM garbage collector.")
			numberDataPoint = metric.Gauge().DataPoints().AppendEmpty()
			numberDataPoint.SetTimestamp(pdata.Timestamp(time.Now().UnixNano()))
			numberDataPoint.SetDoubleVal(float64(v.GetCollectionCount()))
		}
		if len(javaAgentData.JavaMemorys) > 0 {
			metric := metricSlice.AppendEmpty()
			metric.SetDataType(pdata.MetricDataTypeGauge)
			metric.SetName("process.runtime.jvm.memory.area")
			metric.SetUnit("MB")
			metric.SetDescription("MBytes of a given JVM memory area.")
			for _, v := range javaAgentData.JavaMemorys {
				var area string
				if strings.ToLower(v.GetType()) == "heap" {
					area = "heap"
				} else {
					area = "non_heap"
				}
				addMemoryMetric(&metric, area, "max", v.GetMax(), "")
				addMemoryMetric(&metric, area, "used", v.GetUsed(), "")
				addMemoryMetric(&metric, area, "committed", v.GetCommited(), "")
			}
		}
		if len(javaAgentData.JavaMemoryPools) > 0 {
			metric := metricSlice.AppendEmpty()
			metric.SetDataType(pdata.MetricDataTypeGauge)
			metric.SetName("process.runtime.jvm.memory.pool")
			metric.SetDescription("MBytes of a given JVM memory pool.")
			metric.SetUnit("MB")
			for _, v := range javaAgentData.JavaMemoryPools {
				var area string
				if strings.ToLower(v.GetType()) == "heap" {
					area = "heap"
				} else {
					area = "non_heap"
				}
				addMemoryMetric(&metric, area, "max", v.GetMax(), v.GetName())
				addMemoryMetric(&metric, area, "used", v.GetUsed(), v.GetName())
				addMemoryMetric(&metric, area, "committed", v.GetCommited(), v.GetName())
			}
		}

		for _, v := range javaAgentData.JavaCpus {
			metric := metricSlice.AppendEmpty()
			metric.SetDataType(pdata.MetricDataTypeGauge)
			metric.SetName("process.runtime.jvm.cpu.utilization")
			metric.SetUnit("%")
			numberDataPoint := metric.Gauge().DataPoints().AppendEmpty()
			numberDataPoint.SetTimestamp(pdata.Timestamp(time.Now().UnixNano()))
			numberDataPoint.SetDoubleVal(float64(v.GetValue() * 100))
		}
		for _, v := range javaAgentData.JavaThreads {
			var name string
			if v.Name == "Threads/all" {
				name = "process.runtime.jvm.threads.count"
			} else if v.Name == "Threads/Deadlocks/all" {
				name = "process.runtime.jvm.threads.deadlocks.count"
			}
			metric := metricSlice.AppendEmpty()
			metric.SetDataType(pdata.MetricDataTypeGauge)
			metric.SetName(name)
			numberDataPoint := metric.Gauge().DataPoints().AppendEmpty()
			numberDataPoint.SetTimestamp(pdata.Timestamp(time.Now().UnixNano()))
			numberDataPoint.SetDoubleVal(float64(v.GetCount()))
		}

		if len(javaAgentData.JmxDatas) > 0 {
			metric := metricSlice.AppendEmpty()
			metric.SetDataType(pdata.MetricDataTypeGauge)
			metric.SetName("process.runtime.jmx")
			for _, v := range javaAgentData.JmxDatas {
				numberDataPoint := metric.Gauge().DataPoints().AppendEmpty()
				attributes := numberDataPoint.Attributes()
				attributes.InsertString("name", v.GetName())
				numberDataPoint.SetTimestamp(pdata.Timestamp(time.Now().UnixNano()))
				value, _ := strconv.ParseFloat(fmt.Sprintf("%.2f", v.GetValue()), 64)
				numberDataPoint.SetDoubleVal(value)
			}
		}
	}
}

func addMemoryMetric(metric *pdata.Metric, area string, typeStr string, value float32, pool string) {
	numberDataPoint := metric.Gauge().DataPoints().AppendEmpty()
	numberDataPoint.SetTimestamp(pdata.Timestamp(time.Now().UnixNano()))
	attributes := numberDataPoint.Attributes()
	attributes.InsertString("type", typeStr)
	attributes.InsertString("area", area)
	if pool != "" {
		attributes.InsertString("pool", pool)
	}
	value2, _ := strconv.ParseFloat(fmt.Sprintf("%.2f", value), 64)
	numberDataPoint.SetDoubleVal(value2)
}
